//#########################################################################
//                              WackiEngie
//           Copyright (c) 2014 Marc Wacker. All rights reserved.
//#########################################################################

#include "WeInput.h"
#include "WeVirtualInput.h"

#include "WeTime.h"

namespace WackiEngine
{


	Map<String, uint32> FName::_uniqueIds;
	uint32 FName::_nextId = 0;

	FName::FName()
		: _id(0)
	{ }

	FName::FName(const String& str)
	{
		String cpy(str);
		StringUtil::toLowerCase(cpy);

		auto findIter = _uniqueIds.find(cpy);

		if(findIter != _uniqueIds.end())
			_id = findIter->second;
		else
		{
			_id = _nextId;
			_uniqueIds[cpy] = _nextId++;
		}
	}


	VirtualInput::VirtualInput()
	{
		// connect callbacks
		gInput().keyInput.connect(this, &VirtualInput::onKey);
	}

	void VirtualInput::update()
	{
		// for the moment we just run through the events of this frame and trigger the corresponding actions
		// this may not be desirable since two press event's without a release between them may not be expected
		for(auto& action : _actionQueue)
		{
			auto& signal = action.actionBinding->signal[action.eventType];

			if(!signal.empty())
				signal(action.deviceId);
		}

		// TODO: find the best way to process the actionQueue
		// and to fire the correct actions that correspond to the keys
		// TODO: do we want the modifiers to work as masks or do we want modifiers to only fire if modifiers are pressed?
		_actionQueue.clear();


		// generate axis events
		for(AxisBinding& ab : _axisBindings)
		{
			// skip if no one needs this axis
			if(ab.signal.empty())
				continue;

			// sum up the axis value for the current frame
			ab.value = 0.0f;
			for(auto& mapping : ab.mappings)
			{
				ab.value += gInput().getAxis(mapping.key) * mapping.scale;
			}

			// fire event
			ab.signal(ab.value, 0); // TODO: track events based on deviceId
		}
	}

	void VirtualInput::addAction(const String& name)
	{
		// already exists, do nothing
		if(findActionBinding(name) != nullptr)
			return;

		ActionBinding binding;
		binding.name = FName(name);
		binding.isDown = false;
		
		_actionBindings.push_back(binding);
	}

	void VirtualInput::removeAction(const String& name)
	{
		// TODO: implement
		// dont forget to remove all the mappings in the keyactionmap
	}

	void VirtualInput::addAxis(const String& name, bool inverted)
	{
		// already exists, do nothing
		if(findAxisBinding(name) != nullptr)
			return;

		AxisBinding binding;
		binding.name = FName(name);
		binding.value = 0.0f;

		_axisBindings.push_back(binding);
	}

	void VirtualInput::removeAxis(const String& name)
	{
		// TODO: implement
	}

	void VirtualInput::onKey(const KeyEvent& e)
	{
		// do nothing if this key isn't handled by an action
		if(_keyActionReleationships.find(e.key) == _keyActionReleationships.end())
			return;

		// add action events for every action bound to this key
		for(ActionBinding* binding : _keyActionReleationships[e.key]) {

			// go through the action mappings and search for the input chord
			for(const ActionMapping& mapping : binding->mappings)
			{
				// don't consider if chord doesnt match
				if(mapping.key != e.key
					|| (e.isShiftDown() != mapping.shift)
					|| (e.isCtrlDown() != mapping.ctrl)
					|| (e.isAltDown() != mapping.alt))
					continue;

				_actionQueue.push_back(ActionEvent(binding, e.type, e.deviceId));
			}
		}

	}

	void VirtualInput::addActionMapping(const String& actionName, KeyCode key, bool shift, bool ctrl, bool alt)
	{
		ActionBinding* binding = findActionBinding(actionName);
		if(binding == nullptr)
			return;

		binding->mappings.insert(ActionMapping(key, shift, ctrl, alt));

		_keyActionReleationships[key].push_back(binding);
	}

	void VirtualInput::addAxisMapping(const String& axisName, KeyCode key, float scale)
	{
		AxisBinding* binding = findAxisBinding(axisName);
		if(binding == nullptr)
			return;

		binding->mappings.insert(AxisMapping(key, scale));
	}

	ActionBinding* VirtualInput::findActionBinding(const String& name)
	{
		FName searchName(name);
		Vector<ActionBinding>::iterator it = std::find_if(_actionBindings.begin(), _actionBindings.end(), 
			[&searchName](const ActionBinding& ab) { return searchName == ab.name; });
		
		if(it == _actionBindings.end())
			return nullptr;
		else
			return &(*it);
	}

	AxisBinding* VirtualInput::findAxisBinding(const String& name)
	{
		FName searchName(name);
		Vector<AxisBinding>::iterator it = std::find_if(_axisBindings.begin(), _axisBindings.end(),
			[&searchName](const AxisBinding& ab) { return searchName == ab.name; });

		if(it == _axisBindings.end())
			return nullptr;
		else
			return &(*it);
	}


	VirtualInput& gVirtualInput()
	{
		return VirtualInput::instance();
	}

}